Skip to content

feat(consensus): implement ZIP 218 behind a build feature - #851

Open
evan-forbes wants to merge 17 commits into
mainfrom
feat/zip218-blocktime-reduction
Open

feat(consensus): implement ZIP 218 behind a build feature#851
evan-forbes wants to merge 17 commits into
mainfrom
feat/zip218-blocktime-reduction

Conversation

@evan-forbes

@evan-forbes evan-forbes commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Motivation

The current NU7 poll asks whether Zcash should adopt ZIP 218. ZIP 218 reduces the block target spacing from 75 seconds to 25 seconds. It also adds shielded action limits.

NU7 remains a candidate network upgrade. Its rules can change before deployment.

Solution

The off-by-default nu7-experimental Cargo feature implements the current candidate NU7 rules. This branch adds the ZIP 218 rules to that feature. Every rule also requires an exact NU7 activation on the selected network. Mainnet and the default Testnet therefore retain current consensus until a deployment PR sets NU7.

The implementation:

  • changes the post-NU7 target spacing to 25 seconds;
  • changes the difficulty averaging window from 17 to 102 blocks;
  • preserves issuance per unit of time by scaling the subsidy by the spacing ratio;
  • enforces limits of 330 Orchard actions, 300 Sapling inputs and outputs, and 25 Sprout JoinSplits;
  • enforces the global cost limit of 330, with each JoinSplit counting twice;
  • applies the limits to block validation, mempool admission, and block-template selection; and
  • reserves shielded capacity used by the coinbase transaction before it selects mempool transactions.

The implementation derives halving heights from the complete spacing schedule. It also represents pre-anchor funding-stream periods as signed integers. This keeps configured Testnets valid when they activate NU7 before the first halving.

The state migration backfills the wider difficulty context before it runs the startup consistency check. Existing databases therefore retain the required 112 predecessor headers when a node upgrades to an experimental NU7 build.

Zakura already meets or exceeds the ZIP's related node-policy recommendations. It permits reorgs up to 1,000 blocks and retains at least 10,000 blocks. Its native block sync bounds work by bytes and peer capacity. Zakura does not create ordinary wallet transactions, so it has no default wallet expiry delta to change.

Testing

Tests cover both sides of the NU7 activation boundary. They cover the exact subsidy, halving inversion, funding-stream period arithmetic, the 102-block averaging window, every per-pool limit, the global limit, mempool admission, block validation, block-template selection, shielded coinbase capacity, and upgrades from existing databases.

The audit passed:

  • 402 zakura-chain tests with nu7-experimental;
  • 281 zakura-header-chain tests;
  • 216 consensus and 128 RPC tests with nu7-experimental;
  • actionlint .github/workflows/tests-unit.yml;
  • the crates.io publish-graph check; and
  • formatting and diff checks.

The branch uses the current 1.1.0 Zakura consensus-library releases from main.

Changelog

Added docs/changelog/unreleased/851.md.

Specifications & References

Follow-up Work

A deployment PR will replace the experimental feature with finalized NU7 rules and set the activation parameters.

Comment thread crates/zakura-header-chain/src/validation/contextual/constants.rs
Comment thread crates/zakura-chain/src/parameters/network/subsidy.rs
@evan-forbes
evan-forbes force-pushed the feat/zip218-blocktime-reduction branch from e76fe49 to d2a03db Compare September 7, 2026 08:13
Implements ZIP 218 "25-second Block Target Spacing" (zcash/zips#1215),
gated on the new `zip218` cargo feature so a default build keeps today's
consensus. Even in a `zip218` build the rules stay dormant until NU7 has
an activation height on the configured network, which no production
network sets yet.

Block target spacing and issuance:

- `PostNU7PoWTargetSpacing` = 25 seconds, an NU7 era in `target_spacings`,
  and a `NU7PoWTargetSpacingRatio` of 3.
- `halving` folds over the per-era spacings instead of branching on
  Blossom, and `block_subsidy` scales by `era_spacing /
  pre_blossom_spacing`. Both are the spec's segmented sum with the common
  denominator factored out, so a future spacing change is one tuple, not
  another branch. Issuance per unit of wall-clock time is unchanged.

Difficulty:

- `PostNU7PoWAveragingWindow` = 102, and `PoWAveragingWindow` becomes a
  height-dependent function that `AdjustedDifficulty` consults for the
  mean target and the median timespan.
- `POW_ADJUSTMENT_BLOCK_SPAN` now covers the widest window the build can
  use, so a `zip218` build carries 113 predecessors of difficulty context
  from genesis and ignores the entries past the window in force at the
  candidate height. The span stays 28 in a default build.

Shielded action limits:

- Per-block limits of 330 Orchard actions, 300 Sapling spends + outputs,
  and 25 Sprout JoinSplits, plus a global shielded budget of 330 over
  `orchard + sapling_ios + 2 * joinsplits`.
- `shielded_action_limits_are_valid` runs over any iterator of
  transactions: the block verifier passes the whole block, and the
  transaction verifier passes one transaction, so the mempool rejects a
  transaction whose own counts can never be mined.
- `getblocktemplate` tracks the same limits through a `BlockTemplateLimits`
  struct, which replaces the three loose counters and the
  `TryUpdateBlockLimits` trait, so template selection cannot build a block
  the verifier would reject.

The non-consensus constants ZIP 218 recommends scaling (the expiry delta,
`MAX_REORG_LENGTH`, and the block download and retention windows) are left
for a follow-up.
`linked_validation_context` hardcoded 27 predecessors, duplicating
`POW_PREDECESSOR_CONTEXT_SPAN` rather than reading it. The recovery audit
requires exactly `min(finalized_height, POW_PREDECESSOR_CONTEXT_SPAN)`
context rows, so the two disagree as soon as the span changes, and full
state initialization fails its own audit.

Read the constant instead, and derive the boundary test's expectations
from it. Also generalize the tests that hardcoded the 28-block difficulty
adjustment span or the 17-block averaging window, and bump
zakura-consensus to 8.0.0 for the new `TransactionError` variants.
The v1.4.0-rc0 release published the current crate versions without the zip218 features. Publish the stable forms of the affected crates and update each workspace requirement.
@evan-forbes
evan-forbes force-pushed the feat/zip218-blocktime-reduction branch from d40db49 to 123c7cf Compare September 7, 2026 16:11
@evan-forbes evan-forbes added the nu7 label Sep 9, 2026
…-reduction

# Conflicts:
#	Cargo.lock
#	crates/zakura-consensus/Cargo.toml
#	crates/zakura-header-chain/Cargo.toml
#	crates/zakura-network/Cargo.toml
#	crates/zakura-node-services/Cargo.toml
#	crates/zakura-rpc/Cargo.toml
#	crates/zakura-script/Cargo.toml
#	crates/zakura-state/Cargo.toml
#	crates/zakura-utils/Cargo.toml
#	crates/zakurad/Cargo.toml
@evan-forbes
evan-forbes requested a review from a team September 9, 2026 20:49
evan-forbes and others added 2 commits September 10, 2026 01:35
* feat(consensus): implement ZIP 2003 with a configurable deprecation height

Implements ZIP 2003 "Disallow version 4 transactions". Zakura already
rejected V4 transactions at NU7, but the height was welded to the NU7
activation height. The NU7 scope polls offer three deprecation dates, so
`V4Deprecation` names the choice instead:

- `AtNu7`, the ZIP 2003 default and today's behaviour, resolving to the
  NU7 activation height. A network that does not activate NU7 keeps
  accepting V4 transactions.
- `AtHeight(height)`, for a network that activates NU7 and deprecates V4
  transactions later.
- `Never`, for a network that keeps accepting V4 transactions.

`verify_v4_transaction_network_upgrade` now rejects on the resolved
height rather than on the NU7 arm of its network upgrade match, so
Mainnet and the default Testnet keep the rule they have today.

Testnet configures the choice through `testnet_parameters.v4_deprecation`,
which accepts `"nu7"`, `"never"`, or a block height.

* docs(changelog): add the ZIP 2003 fragment

* fix(consensus): require an exact NU7 v4 boundary

* refactor(consensus): fix v4 deprecation at NU7

* refactor(consensus): gate v4 deprecation on experimental NU7
…-reduction

Main's #958 added its own nu7-experimental feature. This merge keeps the
branch's feature lists, which forward to more crates, and removes the
duplicate key from zakurad. The CI job keeps the branch's tests and adds
main's cargo check of the zakura package.

# Conflicts:
#	.github/workflows/tests-unit.yml
#	crates/zakura-consensus/Cargo.toml
#	crates/zakurad/tests/common/configs/v1.4.0.toml
PR #856 merged into this branch, so its ZIP 2003 entry ships with #851.
A PR may carry only its own fragment, so move the entry into 851.md and
delete 856.md.
v1.4.0 published the current crate versions without the nu7-experimental
features, so the publish graph could not resolve them. Patch-bump
zakura-chain, zakura-header-chain and zakura-state, which gain the feature.
Major-bump zakura-consensus, which adds variants to the exhaustive
TransactionError enum. Point each dependent that enables the feature at
the new versions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants